#include "src/scene/hbb/tree.h"

#include <cmath>
#include <iostream>

#include "src/defines/operation.h"

#include "src/math/vector3d.h"

#include "src/scene/object.h"
#include "src/scene/hbb/leaf.h"

HBBTree::HBBTree(HBBBox *_box) : children(NULL), box(_box), internal(false)
{
//    test = t;
}

HBBTree::HBBTree(const std::vector<Object *> &_objects, const unsigned int &_axis) : internal(true)
#if WITHOUT_HBB
, objetos(_objects)
#endif //WITHOUT_HBB
{
//    test = t;
    //_i indica o inicial
    //_j indica o final da lista mais 1, indicando assim o tamanho da região
    unsigned int n = _objects.size();
    children = new HBBTree*[2];
    if(n == 1)
    {
//        std::cout <<"FOLHA " << t + 1 << std::endl;
//        int nhe;
//        std::cin >> nhe;
        children[0] = new HBBLeaf(_objects[0]); //ja tem sua propria box
        children[1] = NULL;

        box = new HBBBox( children[0]->box->getMin(), children[0]->box->getMax() );
    }
    else if(n == 2)
    {
//        std::cout <<"2FOLHA" << std::endl;
//        int nhe;
//        std::cin >> nhe;
        children[0] = new HBBLeaf(_objects[0]); //ja tem sua propria box
        children[1] = new HBBLeaf(_objects[1]); //ja tem sua propria box

        box = new HBBBox( min(children[0]->box->getMin(), children[1]->box->getMin()), max(children[0]->box->getMax(), children[1]->box->getMax()) );
    }
    else
    {
//        std::cout <<"dividindo " << n << std::endl;
//        int nhe;
//        std::cin >> nhe;
        //separar as listas
        double min_ = -INFINITY, max_ = INFINITY;
        for(unsigned int i = 0; i < n; i++)
        {
            const HBBBox &boxAux = _objects[i]->getBoundingBox();
            double minAux = boxAux.getMin()[_axis];
            double maxAux = boxAux.getMax()[_axis];
            if(minAux < min_)
                min_ = minAux;
            if(maxAux < max_)
                max_ = maxAux;
        }
        double midpoint = (min_ + max_);

        std::vector<Object *> left, right;
        int minPosition = -1, maxPosition = -1;
        double minMid = INFINITY, maxMid = -INFINITY;
        for(unsigned int i = 0; i < n; i++)
        {
            const HBBBox &boxAux = _objects[i]->getBoundingBox();
            double midAux = boxAux.getMin()[_axis] + boxAux.getMax()[_axis];

            //para valores para ver quem pode passar pro outro lado
            if(midAux < minMid)
            {
                minMid = midAux;
                minPosition = right.size();
            }
            if(midAux > maxMid)
            {
                maxMid = midAux;
                maxPosition = left.size();
            }


            if( midAux < midpoint )
                left.push_back(_objects[i]);
            else
                right.push_back(_objects[i]);
        }

        if(left.empty()){
            left.push_back(right[minPosition]);
            right.erase(right.begin() + minPosition);
        }
        else if(right.empty()){
            right.push_back(left[maxPosition]);
            left.erase(left.begin() + maxPosition);
        }

        unsigned int axis = (_axis + 1) % 3;
        children[0] = new HBBTree(left, axis);
        children[1] = new HBBTree(right, axis);

        //descobrir qual a bouding box do ó atua baseado nós abaixo
        box = new HBBBox( min(children[0]->box->getMin(), children[1]->box->getMin()), max(children[0]->box->getMax(), children[1]->box->getMax()) );
    }
}

HBBTree::~HBBTree()
{
//    std::cout <<"deletando tree " << test<< std::endl;
    if(children){
        if(children[0]) delete children[0];
        if(children[1]) delete children[1];
        delete [] children;
        children = NULL;
    }

    if(internal){
//        std::cout <<" é interno" << std::endl;
        delete box;
    }
    box = NULL;
}

int HBBTree::draw(const unsigned int &_color) const
{
    if(!box) return 0;

    int depth1 = 0, depth2 = 0;
    if(children)
    {
        if(children[0]) depth1 = children[0]->draw((_color + 2)%7 + 1);
        if(children[1]) depth2 = children[1]->draw((_color + 3)%7 + 1);
    }
    int depth;
    if(depth1 > depth2) depth = depth1;
    else depth = depth2;

    glPushMatrix();
    Vector3D min = box->getMin(), max = box->getMax();
    Vector3D mid = (min + max) * 0.5;
    glTranslated(mid.x, mid.y, mid.z);
    glScaled(1 + depth * 0.1, 1 + depth * 0.1, 1 + depth * 0.1);
    glTranslated(-mid.x, -mid.y, -mid.z);

    box->draw(_color + 1);

    glPopMatrix();

    return depth+1;
}

bool HBBTree::hit(const Ray &_ray, NearestInfo *_nearest, Object *_obj) const
{
    #if WITHOUT_HBB
    bool collided = false;
    for(unsigned int i = 0; i < objetos.size(); i++)
    {
        if(_obj == objetos[i]) continue;
        NearestInfo result;
        if(objetos[i]->hit(_ray, &result))
        {
            collided = true;
            if(result.t < _nearest->t)
            {
                *_nearest = result;
            }
        }
    }
    return collided;

    #else

    if(!box) return false;


    //caso seja um no interno
    if(children)
    {
        if(box->hit(_ray, _nearest))
        {
            NearestInfo leftInfo, rightInfo;
            bool leftHit, rightHit;

            if(children[0]){
                leftHit = children[0]->hit(_ray, &leftInfo, _obj);
            }else{
                leftHit = false;
            }

            if(children[1]){
                rightHit = children[1]->hit(_ray, &rightInfo, _obj);
            }else{
                rightHit = false;
            }

            if(leftHit && rightHit)
            {
                if(leftInfo.t < rightInfo.t)
                    *_nearest = leftInfo;
                else
                    *_nearest = rightInfo;
                return true;
            }
            else if(leftHit)
            {
                *_nearest = leftInfo;
                return true;
            }
            else if(rightHit)
            {
                *_nearest = rightInfo;
                return true;
            }else{
                return false;
            }
        }
        else return false;
    }
    else // no externo
    {
        #if DEBUG
        if(internal) std::cout <<"ELE ESTAH COMO INTERNAL!" << std::endl;
//            else std::cout <<"tah mesmo como external" << std::endl;
        #endif //DEBUG

        Object *obj = getObject();


//        bool test = false;
//        if(_obj) if(_obj->getId() == 1)
//        {
//            test = true;
//        }
//        if(test) std::cout <<"\ttestnado contra o objeto " << obj->getId() << std::endl;

        if(obj == _obj)
        {
            //!TODO ele deveria poder ver se nao bateu nso outros objetos da união
//            if((!obj->isPrimitive()) && (obj->getType() == UNION))
//            {
//                //é oposivel que pegue em algum objeto que seja ele mesmo
//                if(obj->hit(Ray(_ray.getOrigin() + _ray.getDirection()*ERROR*10.0, _ray.getDirection()), _nearest))
//                {
//                    return true;
//                }
//            }
            //ignorando o proprio objeto quando é pro shadowray
            return false;
        }

//
//        if(test) {
//        if(hitouEm) std::cout <<"\t\tbateu no objeto" << _nearest->object->getId() << std::endl;
//        else std::cout <<"\t\tnao bateu em ninguem" << std::endl;
//        }

        return (obj->hit(_ray, _nearest));
    }




    #if DEBUG
    std::cout <<"NAO ERA PRA TAH CHEGANDO AQUI!" << std::endl;
    #endif //DEBUG

    return false;

    #endif //WITHOUT_HBB
}
